<?php
/**
 * Description of Core
 *
 * @author David Dusek
 */
namespace CMS;

use Doctrine\ORM\EntityManager,
    CMS\Module\Models\Module;
    

class Core {
    private static $instance = null;
    /**
     * Entity manager
     * @var Doctrine\ORM\EntityManager
     */
    private $em = null;
    
    /**
     * Pole s hooky.
     * @var array 
     */
    private $hooks = array();
    
    /**
     * Nactene moduly.
     * @var array 
     */
    private $modules = array();
    
    /**
     * Instance jednotlivych modulu. Hooky se pak volaji na tyto instance a 
     * tim je zaruceno ze objekt modulu si udrzuje kontext skrze cele zpracovani
     * pozadavku.
     * @var type 
     */
    private $instancesOfModules = array();
    
    /**
     * Uloziste ktere mohou pouzit jine moduly pro ulozeni dat
     * @var array 
     * TODO: v pripade ze nebude nikde potreba smazat
     */
    private $data = array();
    
    public function setEm(EntityManager $em) {
        $this->em = $em;
    }
    
    public function getEm() {
        return $this->em;
    }
    
    private function __construct() {
        ;
    }
    
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new Core();
        }
        
        return self::$instance;
    }
    
    /**
     * Zapsani do uloziste dat.
     * @param String $name jmeno by melo byt ve tvaru nazevModulu_jmeno
     * @param mixed $data data pro ulozeni pod dane jmeno
     */
    public function setData($name, $data) {
        $this->data[$name] = $data;
    }
    
    /**
     * Ziskani dat z uloziste.
     * @param String $name jmeno 
     * @return mixed Datovou polozku nebo FALSE pokud polozka se zadanym jmenem
     * neexistuje. 
     */
    public function getData($name) {
        if (isset($this->data[$name])) {
            return $this->data[$name];
        }
        
        return FALSE;
    }
    
    /**
     * Nacte vsechny moduly ze slozky modules a prida je do databaze. Nastaveni
     * modulu se ziska z konfiguracniho souboru daneho muodulu. Dale se vytvori
     * seznam hooku, ktere nabizeji jednotlive moduly.
     */
    public function loadModules() {
        if ($this->em === null) {
            return;
        }
        
        $this->em->getConnection()->exec(
            "CREATE TABLE IF NOT EXISTS `module` (
            `module_name` varchar(50) COLLATE utf8_czech_ci NOT NULL,
            `active` tinyint(4) DEFAULT '0',
            `version` varchar(20) COLLATE utf8_czech_ci DEFAULT NULL,
            `author` varchar(100) COLLATE utf8_czech_ci DEFAULT NULL,
            `order` smallint(3) DEFAULT '0',
            `system` tinyint(4) DEFAULT '0',
            PRIMARY KEY (`module_name`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_czech_ci;"
        );
        
        $max = $this->em->getRepository("\CMS\Module\Models\Module")->getMaxOrder();
        $handle = opendir(MODULE_DIR);
        if ($handle) {
            while (false !== ($entry = readdir($handle))) {
                if ($entry != "." && $entry != ".." && is_dir(MODULE_DIR . "/$entry")) {
                    $this->loadOneModule($entry);
                }
            }
        }
                
        $this->em->getRepository("\CMS\Module\Models\Module")->removeMissingModules($this->modules);
        
        // zavolame hook, ktery umozni modulum provest svou inicializaci
        $this->runHook("hookInitModule");
    }
    
    /**
     * Nacte informace o modulu a do atributu hooks prida informace o hook
     * funkcich nabizenych timto modulem. Aktualizuje nebo vytvori zaznam
     * o modulu a jeko konfiguraci v databazi. Inforrmace o nastaveni modulu
     * jsou ziskany z konfiguracniho souboru modulu.
     * @param String $name jmeno modulu
     */
    private function loadOneModule($name) {
        $nameFUC = ucfirst($name);
        $moduleName = $nameFUC . "Module";        
        $moduleFile = MODULE_DIR . "/$name/" . $moduleName . ".php";
        if (is_file($moduleFile)) {            
            $mr = $this->em->getRepository("\CMS\Module\Models\Module");
            $moduleDBObject = $mr->find($nameFUC);
             // modul jeste neni v databazi takze insertneme
            if ($moduleDBObject === null) {
                $moduleDBObject = new Module();
                $moduleDBObject->setName($nameFUC);
                $moduleDBObject->setActive(FALSE);
                $moduleDBObject->setOrder($mr->getMaxOrder());
                
                $this->em->persist($moduleDBObject);
                
                $this->createTables($name);
            }
            $moduleDBObject = $this->loadModuleConfig($moduleDBObject, $name);
            
            // pokud je modul aktivni pridaji se jeho hooky
            if ($moduleDBObject->getActive()) {
                $hooks = $moduleName::getHooks();
                foreach ($hooks as $hook) {
                    $this->hooks[$hook][] = $moduleName;
                }
                
                $moduleClass = "\\$moduleName";
                $this->instancesOfModules[$moduleName] = new $moduleClass;
            }
            
            $this->modules[] = $moduleDBObject;   
            $this->em->flush();
        }
    }
    
    /**
     * Nacte konfiguracni soubor modulu a informace z nej ulozi do odpovidajicich
     * atributu v databazi.
     * @param type $moduleDBObject persistentni objekt modulu
     * @param type $moduleName jmeno modulu
     * @return type 
     */
    private function loadModuleConfig($moduleDBObject, $moduleName) {
        $fileName = MODULE_DIR . "/$moduleName/config.ini";
        if (is_file($fileName)) {
            $config = parse_ini_file($fileName);
            $moduleDBObject->setVersion($config["version"]);
            $moduleDBObject->setAuthor($config["author"]);
            
            if (isset($config["system"]) &&  $config["system"]) {
                $moduleDBObject->setSystem(TRUE);
                $moduleDBObject->setActive(TRUE);
            } else {
                $moduleDBObject->setSystem(FALSE);
            }
        }
                
        return $moduleDBObject;
    }
    
    /**
     * Spusteni hook funkce zadaneho jmena. Pokud neni zadan parametr module
     * je funkce spustena pro vsechny aktivni moduly, ktere tuto funkci 
     * implementuji.
     * @param String $hook nazev hook funkce
     * @param mixed $parameters paramentr nebo pole parametru pro volani hooku
     * @param String $module nazev modulu v kterem se ma funkce spustit
     * @return mixed Vysledek volani funkce nebo FALSE pokud neni mozne
     * pozadovanou hook funkci spustit.
     */
    public function runHook($hook, $parameters = null, $moduleName = null) {
        // pokud dany hook zadny modul neimplementuje tak nedelej nic
        if (!isset($this->hooks[$hook])) {
            return FALSE;
        }
        
        $result = FALSE;
        $modules = $this->hooks[$hook];
        if (count($modules) > 0) {
            $callFor = array();
            if ($moduleName === null) {
                $callFor = $modules;
            } else {
                if (in_array($moduleName, $modules)) {
                    $callFor[] = $moduleName;
                }
            }
            $result = $this->callHook($hook, $callFor, $parameters);
        }
        
        // pokud bylo volano pro urcity modul vratime jen jeho vysledek
        if ($moduleName !== null) {
            $result = $result[$moduleName];
        }
        
        return $result;
    }
    
    /**
     * Vyvola hook funkci v kazdem z modulu predanych v parametru $callFor.
     * Hook funkce je volana s parametrem/parametry predanymi v parametru
     * parameters. Vysledke je bud FALSE pokud neni zadny modul na kterem funkci
     * vyvolat nebo pole vysledku indexovane nazvem modulu.
     * @param type $hook jmeno hook funkce
     * @param type $callFor pole modulu na kterych se ma hook funkce vyvolat
     * @param type $parametrs parametr/parametry pro volani hook funkce
     * @return mixed FALSE pokud neni zadny modul v parametru callFor jinak
     * pole indexovane nazvem modulu s vysledky jednotlivych volani. 
     */
    private function callHook($hook, $callFor, $parametrs) {
        if (count($callFor) <= 0) {
            return FALSE;
        }
        
        $results = array();
        foreach ($callFor as $moduleName) {
            $instance = $this->instancesOfModules[$moduleName];
            if (is_array($parametrs)) {
                $results[$moduleName] = call_user_func_array(array($instance, $hook), $parametrs);
            } else {
                $results[$moduleName] = call_user_func(array($instance, $hook), $parametrs);
            }
        }
        
        return $results;
    }
    
    /**
     * Vraci asociativni pole ve tvaru [nazevModulu] ktere obsahuje asociativni
     * pole s prvky [link] a [title].
     * @return array 
     */
    public function getAdminMenu($userId) {        
        $allowedModules = $this->runHook("hookUserGetAllowModules", $userId, "UserModule");
                
        $results = $this->runHook("hookAdminMenu", null);
        if ($results === FALSE || !is_array($results)) {
            return array();
        }
        
         $qb = $this->em->getRepository("\CMS\Module\Models\Module")->createQueryBuilder("m");
         $modules = $qb->orderBy("m.order")->getQuery()->getResult();
        if (count($modules) <= 0) {
            return array();
        }
        
        $items = array();
        foreach ($modules as $moduleObj) {
            $moduleName = lcfirst($moduleObj->getName());
            $moduleIndex = $moduleObj->getName() . "Module";            
            
            if (isset($results[$moduleIndex]) && array_search($moduleIndex, $allowedModules) !== FALSE) {
                $menuItem = $results[$moduleIndex];
                $items[$moduleName]["title"] = $moduleName . "_" . $menuItem["title"];
                $items[$moduleName]["link"] = $menuItem["link"];
                $items[$moduleName]["activeLink"] = substr($menuItem["link"], 0, (strpos($menuItem["link"], ":", 1) + 1)) . "*";
                $items[$moduleName]["img"] = $menuItem["img"];                
            }
        }
        
        return $items;
    }
    
    /**
     * Vraci asociativni pole se submenu pro dany modul.
     * @param String $moduleName nazev modulu s prvnim velkym pismenem.
     * @return array 
     */
    public function getAdminSubmenu($moduleName, $userId) {
        $allowedOperations = $this->runHook("hookUserGetAllowOperations", $userId, "UserModule");        
        
        $module = $moduleName . "Module";
        $result = $this->runHook("hookAdminSubmenu", null, $module);
        $submenu = array();
        
        if (is_array($result) && count($result) > 0) {
            $submenu = $result;
            foreach ($submenu as $key => $submenuItem) {
                $submenu[$key]["title"] = lcfirst($moduleName) . "_" . $submenuItem["title"];
                
                $linkParts = explode(":", $submenu[$key]["link"]);
                if (count($linkParts) == 4) {
                    $controlStr = $linkParts[1] . ":" . $linkParts[2] . ":" . $submenu[$key]["right"];
                    if (array_search($controlStr, $allowedOperations) === FALSE) {
                        unset($submenu[$key]);
                    }
                }
            }
        }
        
        return $submenu;
    }

    /**
     * Vyhleda ve slozce modulu soubor create.sql a pokud ho najde tak jeho
     * obsah vykona.
     * @param type $modulName 
     */
    private function createTables($modulName) {
        $SQLFile = MODULE_DIR . "/$modulName/create.sql";
        
        if (is_file($SQLFile)) {
            $createScript = file_get_contents($SQLFile);
            $this->em->getConnection()->exec($createScript);
        }
    }
}
